#include "OceanAdvModel.h"

//Include GeoetryGenerator - We use this to create a grid. We then
//use displacement mapping to anaimate it. 
#include "GeometryGenerator.h"

//Include the effect we use to render the object - we also need
//the shader manager to get it. 
#include "ShaderManager.h"
#include "OceanWaveFX.h"

OceanAdvModel::OceanAdvModel()
{
	//Init D3D pointers to NULL.
	wavesVB = 0;
	wavesIB = 0;
	wavesNormalMap0 = 0;
	wavesNormalMap1 = 0;
	cubeMap = 0;
	waveTex = 0;

	//Get the effect needed to render the ocean
	effect = ShaderManager::GetInstance()->ReturnOceanWaveFX();
}

OceanAdvModel::~OceanAdvModel()
{
	//Destroy buffers and textuees. 
	ReleaseCOM(wavesVB);
	ReleaseCOM(wavesIB);
	ReleaseCOM(wavesNormalMap0);
	ReleaseCOM(wavesNormalMap1);
	ReleaseCOM(cubeMap);
	ReleaseCOM(waveTex);
}

void OceanAdvModel::Init(float depth, float width, XMFLOAT3 position,
	 ID3D11Device* device)
{
	if (!isInit)
	{
		//Store height/width
		this->depth = depth;
		this->width = width;

		//Init material. 
		gridMaterial.Ambient  = XMFLOAT4(0.25f, 0.32f, 0.4f, 1.0f);
		gridMaterial.Diffuse  = XMFLOAT4(0.35f, 0.42f, 0.52f, 1.0f); //Opaque. 
		gridMaterial.Specular = XMFLOAT4(0.9f, 0.9f, 0.9f, 128.0f);
		gridMaterial.Reflection  = XMFLOAT4(0.065f, 0.08f, 0.12f, 1.0f);

		//Load geometry. 
		CreateWaveBuffers(device);
		//Load textures
		CreateWaveTextures(device);

		//Generate AABB
		GenerateAABB();
		//Small modification to the aabb (This is disp mapped)
		aabbL.Extents.y+=10.0f;

		//Set world position & generate world matrix. 
		SetWorldPosition(position);

		//Init texture transforms for the waves
		dispOffset0 = XMFLOAT2(0.0f, 0.0f);
		dispOffset1 = XMFLOAT2(0.0f, 0.0f);
		normalOffset0 = XMFLOAT2(0.0f, 0.0f);
		normalOffset1 = XMFLOAT2(0.0f, 0.0f);

		XMMATRIX ident = XMMatrixIdentity();
		XMStoreFloat4x4(&dispOffset0Mat, ident);
		XMStoreFloat4x4(&dispOffset1Mat, ident);
		XMStoreFloat4x4(&normalOffset0Mat, ident);
		XMStoreFloat4x4(&normalOffset1Mat, ident);

		//Load cubemap.
		HR(D3DX11CreateShaderResourceViewFromFile(device, 
			L"Textures/snowcube1024.dds",
			0, 0, &cubeMap,
			0));

		//Load texture. 
		HR(D3DX11CreateShaderResourceViewFromFile(device, 
			L"Textures/OceanBlueTex.png", 
			0, 0, &waveTex, 
			0));

		//Set the init flag once we have loaded the data
		isInit = true;
	}
	else
	{
		OutputDebugStringW(L"OceanAdvModel Error 1 - Initing ocean which has already been inited.\n");
		while(1){}
	}
}

void OceanAdvModel::CreateWaveBuffers(ID3D11Device* device)
{
	//Create a grid. 
	GeometryGenerator::MeshData data;
	GeometryGenerator geoGen;

	geoGen.CreateGrid((float)width, (float)depth, (UINT)width, (UINT)depth, data);

	std::vector<OceanWaveFX::PerVertexData>vertex(data.Vertices.size());

	//Extract vertex data,
	for (unsigned i = 0; i < data.Vertices.size(); i++)
	{
		//Copy the position to our vector
		sysCopyVerts.push_back(data.Vertices[i].Position);

		vertex[i].PosL = data.Vertices[i].Position;
		vertex[i].NormalL = data.Vertices[i].Normal;
		vertex[i].Tex  = data.Vertices[i].TexC;
		vertex[i].TangentL = data.Vertices[i].TangentU;
	}

	//Create buffer. 
	D3D11_BUFFER_DESC bd;
	bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	bd.ByteWidth = sizeof(OceanWaveFX::PerVertexData) * data.Vertices.size();
	bd.CPUAccessFlags = NULL;
	bd.MiscFlags = NULL;
	bd.StructureByteStride = 0;
	bd.Usage = D3D11_USAGE_IMMUTABLE;

	D3D11_SUBRESOURCE_DATA sd;
	sd.pSysMem = &vertex[0];

	HR(device->CreateBuffer(&bd, &sd, &wavesVB));

	//Create index buffer.
	bd.BindFlags = D3D11_BIND_INDEX_BUFFER;
	bd.ByteWidth = sizeof(UINT) * data.Indices.size();

	sd.pSysMem = &data.Indices[0];

	HR(device->CreateBuffer(&bd, &sd, &wavesIB));

	//Store grid index count
	wavesNumIndicies = data.Indices.size();
}

void OceanAdvModel::CreateWaveTextures(ID3D11Device* device)
{
	HR(D3DX11CreateShaderResourceViewFromFile(device, 
		L"Textures/waves0.dds", 0, 0, &wavesNormalMap0, 0));
	HR(D3DX11CreateShaderResourceViewFromFile(device, 
		L"Textures/waves1.dds", 0, 0, &wavesNormalMap1, 0));
}

void OceanAdvModel::Update(float delta)
{
	if (isInit)
	{
		//Animation timer - How long we the animation has been playing
		//in seconds. Used to work to work out a value to pass in to sin/cos/tan
		static float animTimer = 0.0f;

		//Update timer
		animTimer+=delta;
		
		//Ensure timer ! > 2.0f. If so,reset. 
		if (animTimer >= 2.0f)
			animTimer-= 2.0f;

		//Generate a value in the range -360.0f -> +360.0f based on animation time. 
		//Thus, if animTimer==2.0f, then we generate 360.0f, and if animTimer==0.0f, 
		//the value is -360.0f;
		float deg = (animTimer*360.0f)-360.0f;

		float sinFac = sin(MathHelper::ToRadians(deg)) * 0.5f + 0.5f * 2; //From a range of [-1,1] to [0,1]
		float cosFac = cos(MathHelper::ToRadians(deg)) * 0.5f + 0.5f;
		float tanFac = tan(MathHelper::ToRadians(deg)) * 0.5f + 0.5f;

		sinFac = 1.0f;

		//Update the texture transformation matricies.
		dispOffset0.x -= (0.010f*sinFac)*delta;
		dispOffset0.y += (0.016f*sinFac)*delta;

		dispOffset1.x += (0.015f*sinFac)*delta;
		dispOffset1.y -= (0.026f*sinFac)*delta;

		XMMATRIX waveScale0  = XMMatrixScaling(0.50f, 0.25f, 1.0f);
		XMMATRIX waveOffset0 = XMMatrixTranslation(dispOffset0.x, dispOffset0.y, 0.0f);

		XMStoreFloat4x4(&dispOffset0Mat, XMMatrixMultiply(waveScale0, waveOffset0));

		XMMATRIX waveScale1  = XMMatrixScaling(0.50f, 0.25f, 1.0f);
		XMMATRIX waveOffset1 = XMMatrixTranslation(dispOffset1.x, dispOffset1.y, 0.0f);
		XMStoreFloat4x4(&dispOffset1Mat, XMMatrixMultiply(waveScale1, waveOffset1));

		normalOffset0.x += (0.04f*sinFac)*delta;
		normalOffset0.y += (0.06f*sinFac)*delta;

		normalOffset1.x -= (0.05f*sinFac)*delta;
		normalOffset1.y += (0.09f*sinFac)*delta;

		//waveScale0  = XMMatrixScaling((float)width/4.0f, (float)depth/4.0f, 1.0f);
		waveScale0  = XMMatrixScaling(4.0f, 4.0f, 1.0f);
		waveOffset0 = XMMatrixTranslation(normalOffset0.x, normalOffset0.y, 0.0f);
		XMStoreFloat4x4(&normalOffset0Mat, XMMatrixMultiply(waveScale0, waveOffset0));

		//waveScale1  = XMMatrixScaling((float)width/3.0f, (float)depth/3.0f, 1.0f);
		waveScale1  = XMMatrixScaling(3.0f, 3.0f, 1.0f);
		waveOffset1 = XMMatrixTranslation(normalOffset1.x, normalOffset1.y, 0.0f);
		XMStoreFloat4x4(&normalOffset1Mat, XMMatrixMultiply(waveScale1, waveOffset1));
	}
}

void OceanAdvModel::Render(bool isZPrePass, bool isOpaquePass, bool isShadowPass, 
		VirtualCamera* playerCamera,										  
		DirecLightShadowMapData* shadowMap,									   
		SSAOData* ssaoMap,									   
		ID3D11ShaderResourceView* envMap,									    
		ID3D11ShaderResourceView* llb,  //In our case, not used as it is automatically uploaded by the ShaderManager                                        
		ID3D11ShaderResourceView* llibO, ID3D11ShaderResourceView* llsebO,     
		ID3D11ShaderResourceView* llibT, ID3D11ShaderResourceView* llsebT,     
		DirectionalLight* dLightArr, unsigned dLightCount,					   
		XMFLOAT3 &hemiColDown, XMFLOAT3 &hemiColRange,
		CXMMATRIX &view, CXMMATRIX &proj,									  
		ID3D11DeviceContext* context)
{
	//Ocean is an opaque piece of geometry - only render when we should.  
	if (isOpaquePass && isInit)
	{
		//Set the correct technique depending. Note: Wave is not a shadow
		//casting object so we return before rendering if this is the shadow
		//pass (its too f*cking expensive)
		if (isShadowPass)
			return; 
		else
		{
			if (isZPrePass)
				effect->SetActiveTechniqueByName("ZPrePass");
			else
				effect->SetActiveTechniqueByName("Tex_CubeMap");
		}

		context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST);
		context->IASetInputLayout(effect->ReturnInputLayout());

		UINT stride = sizeof(OceanWaveFX::PerVertexData);
		UINT offset = 0;
		context->IASetVertexBuffers(0, 1, &wavesVB, &stride, &offset);
		context->IASetIndexBuffer(wavesIB, DXGI_FORMAT_R32_UINT, 0);

		//Work out global world matrix
		XMMATRIX wd = XMLoadFloat4x4(&worldMatrix);
		XMMATRIX wit = MathHelper::InverseTranspose(wd);
		XMMATRIX wvp = (wd * view) * proj;
		XMMATRIX wv = wd * view;
		//SSAO Transform. 
		XMMATRIX ssaoTrans = wvp * TO_TEX_SPACE;

		//Set variables.
		effect->SetCbufferTessData(OCEAN_ADV_MODEL_HEIGHT_SCALE_0, OCEAN_ADV_MODEL_HEIGHT_SCALE_1, 
			2.0f, 30.0f, 12.0f, 6.0f);
		effect->SetShaderResources(wavesNormalMap0, wavesNormalMap1, cubeMap, waveTex); //use own cube map. 
		effect->SetPerFrameCbuffer(playerCamera->GetPosition(), dLightArr, dLightCount, llibO, llsebO);
		effect->SetHemisphericalData(hemiColDown, hemiColRange);
		effect->SetSSAOData(ssaoMap->ReturnSSAOMap(), ssaoTrans);

		if ((!isShadowPass) && (shadowMap))
		{
			effect->SetShadowMap(shadowMap->GetShadowMapSRV());
			effect->SetShadowMapMatricies(shadowMap->GetLightView(), shadowMap->GetLightProj(), shadowMap->GetNDCToTex());
		}
		else
		{
			XMMATRIX i = XMMatrixIdentity();
			effect->SetShadowMap(0);
			effect->SetShadowMapMatricies(i, i, i);
		}

		effect->SetPerObjectCbuffer(wd,
			view*proj,
			wit,
			XMMatrixMultiply(wd, (view*proj)),
			XMLoadFloat4x4(&normalOffset0Mat),
			XMLoadFloat4x4(&normalOffset1Mat),
			XMLoadFloat4x4(&dispOffset0Mat), 
			XMLoadFloat4x4(&dispOffset1Mat),
			XMMatrixScaling(1.0f, 1.0f, 1.0f),
			gridMaterial, 
			wv, 
			view);

		//Draw
		for (unsigned p = 0; p < effect->ReturnActiveTechniqueNumberOfPasses(); p++)
		{
			effect->ApplyActiveTechniqueForPass(p, context);
			context->DrawIndexed(wavesNumIndicies, 0, 0);
		}

		//Unbind resources.
		ID3D11ShaderResourceView* srvNULL[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
		context->PSSetShaderResources(0, 9, srvNULL);

		//Reset HullShader and DomainShader states.
		context->HSSetShader(NULL, NULL, NULL);
		context->DSSetShader(NULL, NULL, NULL);
	}
}